package win.larryzeal.mybatis.config;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.LogFilter;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.util.Arrays;

// import org.springframework.core.annotation.Order;

/**
 * Created by 张少昆 on 2017/8/15.
 */
@ComponentScan( basePackages = "win.larryzeal.mybatis" )
@Configuration
@PropertySource( "classpath:application.properties" )
public class AppConfig {
    @Value( "${jdbc.driverClass}" )
    private String driverClass;
    @Value( "${jdbc.url}" )
    private String url;
    @Value( "${jdbc.username}" )
    private String username;
    @Value( "${jdbc.password}" )
    private String password;
    @Value( "${jdbc.validationQuery}" )
    private String validationQuery;

    /**
     * 记录超过30分钟的SQL
     */
    @Bean
    public StatFilter statFilter(){
        StatFilter statFilter = new StatFilter();
        statFilter.setSlowSqlMillis(30 * 60 * 1000);
        statFilter.setLogSlowSql(true);
        return statFilter;
    }

    /**
     * 日志输出配置
     */
    @Bean
    public LogFilter logFilter(){
        Slf4jLogFilter logFilter = new Slf4jLogFilter();
        // 关闭错误日志输出
        logFilter.setStatementLogErrorEnabled(false);
        return logFilter;
    }

    @Bean( name = "dataSource", initMethod = "init", destroyMethod = "close" )
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        // 自动提交
        dataSource.setDefaultAutoCommit(true);
        // 配置初始化大小、最小、最大
        dataSource.setInitialSize(2);
        dataSource.setMinIdle(2);
        dataSource.setMaxActive(100);
        // 查询\事务超时时间（半小时）
        // dataSource.setQueryTimeout(1800000);
        // 配置获取连接等待超时的时间
        dataSource.setMaxWait(60 * 1000);
        // 配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位是毫秒
        dataSource.setTimeBetweenEvictionRunsMillis(60000);
        // 配置一个连接在池中最小生存的时间，单位是毫秒
        dataSource.setMinEvictableIdleTimeMillis(1800000);
        dataSource.setValidationQuery(validationQuery);
        dataSource.setValidationQueryTimeout(3000);
        dataSource.setTestWhileIdle(true);
        dataSource.setTestOnBorrow(false);
        dataSource.setTestOnReturn(false);
        // 打开PSCache，并且指定每个连接上PSCache的大小
        dataSource.setPoolPreparedStatements(true);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
        // 配置监控统计拦截的 filters
        dataSource.setProxyFilters(Arrays.asList((Filter) statFilter(), logFilter()));
        // 合并多个DruidDataSource的监控数据
        dataSource.setUseGlobalDataSourceStat(true);
        // 配置这个属性的意义在于，如果存在多个数据源，监控的时候可以通过名字来区分开来
        dataSource.setName("dataSource");

        return dataSource;
    }

    @Bean( name = "sqlSessionFactory" )
    public SqlSessionFactory sqlSessionFactory() throws Exception{
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("spring-mybatis.xml"));

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:win/larryzeal/mybatis/mapper/*.xml"));

        return sqlSessionFactoryBean.getObject();
    }

    @Bean( name = "sqlSession", destroyMethod = "clearCache" )
    @Scope( BeanDefinition.SCOPE_PROTOTYPE )
    public SqlSessionTemplate sqlSession() throws Exception{
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    /**
     * 声明式事务
     */
    @Bean( name = "transactionManager" )
    // @Order( 20 )
    public DataSourceTransactionManager dataSourceTransactionManager(){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource());
        return dataSourceTransactionManager;
    }

    @Configuration
    @MapperScan( basePackages = {"win.larryzeal.mybatis.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory" )
    static class MyBatisMapperConfig {
        // MapperScan注解不要和数据源定义的配置写在一起，（如MybatisH2Config配置上），
        // 否此会导致循环引用初始化bean问题．
        // 看来xml配置还是有优势的

        // 03-11 17:01:50.010 main WARN o.s.b.f.s.DefaultListableBeanFactory - Bean creation
        // exception on FactoryBean type check:
        // org.springframework.beans.factory.BeanCreationException: Error creating bean with name
        // 'userMapper' defined in file
        // [/home/cui/workspace/spring4-2015/target/classes/com/doctor/spring4/common/mapper/UserMapper.class]:
        // Cannot resolve reference to bean 'dbH2SqlSessionFactory' while setting bean property
        // 'sqlSessionFactory'; nested exception is
        // org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean
        // with name 'dbH2SqlSessionFactory': Requested bean is currently in creation: Is there an
        // unresolvable circular reference?
    }
}
